#include "Environment.hpp"
#include "EvolutionaryException.hpp"
//#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <iostream>

using std::vector;

//#define OCTAVE_OUTPUT

///\brief Method reinitialising the environment
void Environment::reboot()
{
  _flags=0;
  _epsilonX=0;
  _epsilonY=0;
  _maxIterations=0;
  _iteration=0;
  _maxPopulation=0;
  _dimensionality=0;
  _mutationProbability=0;
  _crossoverProportion=0;
  _parents.clear();
  _children.clear();
}

///\brief Destructor
Environment::~Environment()
{
}  


///\brief Setter method for maximum number of specimens in population
void Environment::setMaxPopulation(unsigned maxPopulation)
{
  if(maxPopulation>0)
    {
      _maxPopulation=maxPopulation;
      _flags |= MAX_POPULATION_SET;
    }
  else
    {
      _flags ^= ~MAX_POPULATION_SET;
      EvolutionaryException ex(0/*tu właściwy kod, może kiedyś*/,
			       "Maximal population size has to be greater than \
zero",
			       __func__,__FILE__,__LINE__);
      throw ex;
    }
}
/*
///\brief Setter method for fitness evaluation function
void Environment::setEvaluationFunction(EvalFunction *function)
{
  _evaluationFunction=function;
  _dimensionality=_evaluationFunction->getDimensionality();
}
*/

///\brief Method for making one evaluation-selection-evolution step
void Environment::makeGeneration(EvalFunction &evalFunction)
{
  selection();
  recombination();
  evaluation(evalFunction);
  ++_iteration;
}

///\brief Method for making one evaluation-selection-evolution step
void Environment::makeGeneration(EvalFunction &evalFunction,
				 std::ostream &stream)
{
  makeGeneration(evalFunction);
  
  sort(_children.begin(),_children.end());
  stream<<"g("<<_iteration+1<<",:,:)=[";
  for(unsigned i=0;i<_children.size();++i)
    {
      for(unsigned j=0;j<_dimensionality;++j)
	{
	  stream<<_children[i][j]<<" ";
	}
      stream<<std::endl;
    }
  stream<<"];"<<std::endl;
  stream<<"v("<<_iteration+1<<",:,:)=[";
  for(unsigned i=0;i<_children.size();++i)
    {
      stream<<i+1<<" "<<_children[i].getFitness()<<std::endl;
    }
  stream<<"];"<<std::endl;
  
  ++_iteration;

}

///\brief Method evaluating all children specimens. 
void Environment::evaluation(EvalFunction &evalFunction)
{
  for(unsigned i=0;i<_children.size();++i)
    {
      evalFunction(_children[i]);
    }
}

///\brief Method recombinating selected parents
void Environment::recombination()
{
  _children.clear();

  std::vector<Specimen> matingPool;
  std::vector<Specimen> notMated;

  while(!_parents.empty())
    {
      if((rand()/(double)RAND_MAX)<_crossoverProbability)
	{
	  matingPool.push_back(_parents.back());
	}
      else
	{
	  notMated.push_back( _parents.back());
	}
      _parents.pop_back();
    }
  


  while(matingPool.size()>1)
    {
      int n=rand()%matingPool.size();
      Specimen parentA=matingPool[n];
      matingPool.erase(matingPool.begin()+n);
      
      n=rand()%matingPool.size();
      Specimen parentB=matingPool[n];
      matingPool.erase(matingPool.begin()+n);
      
      Specimen childA,childB;
      parentA.algebraicDetCrossover(parentB,childA,childB,_crossoverProportion);
      
      _children.push_back(childA);
      _children.push_back(childB);
    }
  if(!matingPool.empty())
    {
      // _children
      _children.push_back(matingPool[0]);
      matingPool.pop_back();
    }

  while(!notMated.empty())
    {
      _children.push_back(notMated.back());
      notMated.pop_back();
    }
  {
    //double randomNumber;  
    //     for(unsigned i=0;i<_children.size();++i)
    //   {
    // 	randomNumber=rand()/((double)RAND_MAX);
    // 	if(randomNumber<_mutationProbability)
    // 	{
    // 	  //	  std::cerr<<"mutek"<<std::endl;
    // 	  _children[i].uniformMutation(_minX,_maxX);
    // 	}
    // }

    if((rand()/(double)RAND_MAX)<_mutationProbability)
      {
	int index=rand()%_children.size();
	_children[index].uniformMutation(_minX,_maxX);
      }
    
  }

}

///\brief Setter method for fitness change stop condition
void Environment::setFitnessMinChange(const Gene& epsilonY)
{
  if(epsilonY>0)
    {
      _epsilonY=epsilonY;
      _flags |= EPS_Y_SET;
    }
  else
    {
      EvolutionaryException ex(0/*tu właściwy kod, może kiedyś*/,
			       "Minimal fitness change has to be greater than \
zero",
			       __func__,__FILE__,__LINE__);
      _flags ^= ~EPS_Y_SET;
      throw ex;
    }
}

///\brief Setter method for distance between specimens stop condition
void Environment::setCoordinatesMinChange(const Gene &epsilonX)
{
  if(epsilonX>0)
    {
      _epsilonX=epsilonX;
      _flags |= EPS_X_SET;
    }
  else
    {
      EvolutionaryException ex(0/*tu właściwy kod, może kiedyś*/,
			       "Minimal coordinates change has to be greater\
 than zero",
			       __func__,__FILE__,__LINE__);
      _flags ^= ~EPS_Y_SET;
      throw ex;
    }
}

///\brief Setter function for number of iterations stop condition
void Environment::setMaxIterations(const long& iterations)
{
  if(iterations)
    {
      _maxIterations=iterations;
      _flags |= MAX_ITER_SET;
    }
  else
    {
      EvolutionaryException ex(0/*tu właściwy kod, może kiedyś*/,
			       "Maximal iteration number has to be greater \
than zero",
			       __func__,__FILE__,__LINE__);
      _flags ^= ~MAX_ITER_SET;
      throw ex;
    }
}

///\brief Setter function for mutation probability
void Environment::setMutationProbability(const double &probability)
{
  if(probability >=0 && probability <=1)
    {
      _mutationProbability=probability;
      _flags |= MUT_PROBAB_SET;
    }
  else
    {
      EvolutionaryException ex(0/*tu właściwy kod, może kiedyś*/,
			       "Probability value should lie between 0 and 1",
			       __func__,__FILE__,__LINE__);
       _flags ^= ~MUT_PROBAB_SET;
       throw ex;
    }
}

///\brief Setter function for crossover probability
void Environment::setCrossoverProbability(const double &probability)
{
  if(probability >=0 && probability <=1)
    {
      _crossoverProbability=probability;
      _flags |= CROSS_PROBAB_SET;
    }
  else
    {
      EvolutionaryException ex(0/*tu właściwy kod, może kiedyś*/,
			       "Probability value should lie between 0 and 1",
			       __func__,__FILE__,__LINE__);
       _flags ^= ~CROSS_PROBAB_SET;
       throw ex;
    }
}

///\brief Setter function for higher bounds of problem domain  
void Environment::setMaxX(const vector<Gene> &maxX)
{
  if(maxX.size() != _dimensionality)
    {
      EvolutionaryException ex(0,"vector size is not equal to set\
 dimensionality",
			       __func__,__FILE__,__LINE__);
      _flags ^= ~MAX_X_SET;
      throw ex;
    }
  _flags |= MAX_X_SET;
  _maxX=maxX;
}

///\brief Setter function for lower bounds of problem domain
void Environment::setMinX(const vector<Gene> &minX)
{
  if(minX.size() != _dimensionality)
    {
      EvolutionaryException ex(0,"vector size is not equal to set\
 dimensionality",
			       __func__,__FILE__,__LINE__);
        _flags ^= ~MIN_X_SET;
      throw ex;
    }
  _flags |= MIN_X_SET;
  _minX=minX;
}

/*!                                                                            
 * \brief Method checking if all parameters are prepared and generating the    
 * first generation of specimens                                               
 */

void Environment::prepare(EvalFunction &evalFunction)
{
  if((_flags & 0xFF)!=0xFF)
    {
      /*tu za każdy brak jedynki wywalamy exception */
      EvolutionaryException ex(" ");
      ex._file=__FILE__;
      ex._line=__LINE__;
      ex._function=__func__;
      
      if(~_flags & DIMS_SET)
	{
	  ex._message="Environment not ready: dimensionality not set";
	}

      else if(~_flags & EPS_X_SET)
	{
	  ex._message="Environment not ready: min coordinate change not set";
	}

      else if(~_flags & EPS_Y_SET)
	{
	  ex._message="Environment not ready: min fitness change not set";
	}

      else if(~_flags & MAX_ITER_SET)
	{
	  ex._message="Environment not ready: max iterations not set";
	}
      
     else if(~_flags & MAX_POPULATION_SET)
	{
	  ex._message="Environment not ready: max population not set";
	}

     else if(~_flags & MUT_PROBAB_SET)
	{
	  ex._message="Environment not ready: mutation probability not set";
	}

     else if(~_flags & CROSS_PROP_SET)
	{
	  ex._message="Environment not ready: crossover proportion not set";
	}

     else if(~_flags & MAX_X_SET)
	{
	  ex._message="Environment not ready: max x vector not set";
	}

     else if(~_flags & MIN_X_SET)
	{
	  ex._message="Environment not ready: min x vector not set";
	}
     else if(~_flags & CROSS_PROBAB_SET)
       {
	 ex._message="Environment not ready: crossover probability not set";
       }
     else
       {
	 ex._code=_flags;
	 ex._message="Environment not ready: don't know why";
       }
      throw ex;
    }

  _children.resize(_maxPopulation);
  for(unsigned i=0;i<_children.size();++i)
    {
      _children[i].initRandom(_dimensionality,_minX,_maxX);
    }
  evaluation(evalFunction);
}

void Environment::setDimensionality(const unsigned &dims)
{ 
  if(dims>0)
    {
      _dimensionality=dims;
      _flags |= DIMS_SET;
    }
  else
    {
      _flags ^= ~DIMS_SET;
      EvolutionaryException ex(0/*tu właściwy kod, może kiedyś*/,
			       "Dimensionality has to be greater than \
zero",
			       __func__,__FILE__,__LINE__);
      throw ex;
    }
  
}

void Environment::setAlgebraicCrossoverProportion(const double &proportion)
{
  if(proportion >=0 && proportion <=1)
    {
      _crossoverProportion=proportion;
      _flags |= CROSS_PROP_SET;
    }
  else
    {
      EvolutionaryException ex(0/*tu właściwy kod, może kiedyś*/,
			       "Proportion value should lie between 0 and 1",
			       __func__,__FILE__,__LINE__);
       _flags ^= ~CROSS_PROP_SET;
       throw ex;
    }
}

///\brief Method returning vector with newest generation                       
std::vector<Specimen> Environment::getChildren()
{
  //  sort(_children.begin(),_children.end());
 return _children;
}

//brief Method preforming the search for optimum
Specimen Environment::search(EvalFunction &evalFunction)
{
  prepare(evalFunction);

  Specimen best;
  sort(_children.begin(),_children.end());
  best=_children.back();

  // std::cerr<<"iteration: "<<_iteration<<std::endl;
  // std::cerr<<"eps X: "<< best.measureDistance(_children.front())<<std::endl;
  // std::cerr<<"eps Y: "<<abs(best.getFitness()
  // 				-_children.front().getFitness())<<std::endl;

  bool flag=true;

 
  while(flag)
    {

      best=_children.front();
      makeGeneration(evalFunction);
      sort(_children.begin(),_children.end());
      
      flag &= _iteration<_maxIterations;
      //      std::cerr<<"iteration: "<<_iteration<<std::endl;
      if(_flags & EPS_X_SET)
	{
	  flag &= _epsilonX < best.measureDistance(_children.front());
	  // std::cerr<<"eps X: "<< best.measureDistance(_children.front())
	  // 	   <<std::endl;
	}

      if(_flags & EPS_Y_SET)
	{
	 flag &= _epsilonY<abs(best.getFitness()
			       -_children.front().getFitness());
	  // std::cerr<<"eps Y: "<<abs(best.getFitness()
	  // 			    -_children.front().getFitness())<<std::endl;

	}

      
    }


  std::cerr<<"iteration: "<<_iteration<<std::endl;
  return best;
}



//brief Method preforming the search for optimum
Specimen Environment::search(EvalFunction &evalFunction,
			     std::ostream &stream)
{
  prepare(evalFunction);
  
  Specimen best;
  sort(_children.begin(),_children.end());
  best=_children.back();

  bool flag=true;

  stream<<"g=zeros("<<_maxIterations<<','<<_maxPopulation<<','
	       <<_dimensionality<<");"<<std::endl;
  stream<<"v=zeros("<<_maxIterations<<','<<_maxPopulation<<','
	       <<2<<");"<<std::endl;


  while(flag)
    {

      best=_children.front();
      makeGeneration(evalFunction,stream);
      sort(_children.begin(),_children.end());
      
      flag &= _iteration<_maxIterations;
      if(_flags & EPS_X_SET)
	{
	  flag &= _epsilonX < best.measureDistance(_children.front());
	}

      if(_flags & EPS_Y_SET)
	{
	 flag &= _epsilonY<abs(best.getFitness()
			       -_children.front().getFitness());
	}

      
    }


  stream<<"figure(1);\nhold on;\n"<<std::endl;
  stream<<"xlabel(\'generation\');\nylabel(\'x\');\nzlabel(\'y\');"
	       <<std::endl;
  stream<<"figure(2);\nhold on;\n"<<std::endl;
  stream<<"xlabel(\'generation\');\nylabel(\'specimen number\');\n\
zlabel(\'f(x)\');"
	       <<std::endl;
  for(unsigned i=1;i<=_iteration;++i)
    {
      stream<<"figure(1)"<<std::endl;
      stream<<"plot3(ones(size(g(1,:,1)))*"<<i<<",g("<<i<<",:,1),g("
		   <<i<<",:,2),'.')"<<std::endl;
      stream<<"figure(2)"<<std::endl;
      stream<<"plot3(ones(size(v(1,:,1)))*"<<i<<",v("<<i<<",:,1),v("
		   <<i<<",:,2),'o')"<<std::endl;

    }
  stream<<"pause()"<<std::endl;



  std::cerr<<"iteration: "<<_iteration<<std::endl;
  return best;
}
